package com.hfb.mashibing.alip8.interview.j2se;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * ArrayList在循环过程中删除中出现的问题
 *
 * ArrayList在循环过程中删除元素，会不会出问题，为什么？
 */
public class T02_ArrayList_for_in_delete {

    static List<String> list = new ArrayList<String>(){{
        add("111");
        add("222");
        add("222");
        add("333");
        add("444");
        add("333");
    }};

    public static void main(String[] args) {
        // for循环正向删除
        forPositiveDelete();
        // for循环反向循环删除
        forReverseDelete();
        // 反向遍历删除（多线程）
        forReverseDelete_MultiThreads();
        // Iterator循环删除
        iteratorDelete();
        // forEach循环删除
        forEachDelete();

    }

    /**
     * for循环正向删除
     *
     * 运行后，输出结果：
     * [111, 222, 333, 444, 333]
     * 发现问题：相邻的字符串“222”没有删除，这是为什么呢？
     * 解释：删除元素“222”，当循环到下标为1的元素的的时候，发现此位置上的元素是“222”，
     * 此处元素应该删除，根据上图中的元素移动可知，在删除元素后面的所有元素都要向前移动一个位置，
     * 那么移动之后，原来下标为2的元素“222”，此时下标为1，这是在i = 1,时的循环操作，
     * 在下一次的循环中，i = 2，此时就遗漏了第二个元素“222”。
     *
     * 那么再做下一个测试，删除元素“333”，结果将如何？
     * 没有任何问题！可以删除索引的元素"333"
     *
     * 结论：for循环正向删除，会遗漏连续重复的元素
     */
    public static void forPositiveDelete(){
        String deleteEle = "222";
//        String deleteEle = "333";
        for (int i = 0; i < list.size(); i++) {
            if(list.get(i).equalsIgnoreCase(deleteEle)){
                list.remove(i);
            }
        }
        System.out.println(Arrays.toString(list.toArray()));
    }

    /**
     * for循环反向循环删除
     * 运行结果：
     *
     * [111, 333, 444, 333]
     *  发现，没有问题。还是画图解释
     *
     *  原因：反向删除的时候，循环遍历完了的元素下标才有可能移动
     *  （已经遍历的元素，下标变化了也没有影响），所以没有遍历的下标不会移动，
     *  自反向删除会遍历到所有的元素，正向会跳过一些元素。
     *
     * 总结：反向遍历删除，没有问题（单线程）
     *
     */
    public static void forReverseDelete(){
        String deleteEle = "222";
//        String deleteEle = "333";
        for (int i = list.size() - 1;i >= 0;i--){
            if(list.get(i).equalsIgnoreCase(deleteEle)){
                list.remove(i);
            }
        }
        System.out.println(Arrays.toString(list.toArray()));
    }

    /**
     * 反向遍历删除（多线程）
     * 运行结果：
     * [444]
     * 结论：多线程反向遍历删除，没有问题
     */
    public static void forReverseDelete_MultiThreads(){

        Thread thread1 = new Thread() {
            @Override
            public void run() {
                remove(list,"111");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                remove(list, "222");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread3 = new Thread() {
            @Override
            public void run() {
                remove(list, "333");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 使各个线程处于就绪状态
        thread1.start();
        thread2.start();
        thread3.start();
        // 等待前面几个线程完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Arrays.toString(list.toArray()));
    }
    public static void remove(List<String> list, String elem) {
        // 普通for循环倒序删除，删除过程中元素向左移动，不影响连续删除
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i).equals(elem)) {
                list.remove(list.get(i));
            }
        }
    }

    /** Iterator循环删除
     *
     * 运行结果：
     *
     * Exception in thread "main" java.util.ConcurrentModificationException
     *     at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
     *     at java.util.ArrayList$Itr.next(ArrayList.java:859)
     *     at joe.effective.Test.main(Test6.java:20)
     *  这个问题就要借助源码来分析了（JDK1.8）:
     *  可以看出，ArrayList的remove方法，一种是根据下标删除，一种是根据元素删除。
     *  发现即使看了remove方法的源码也不能找到报错的原因，
     *  由于我们使用了Iterator迭代器，那么再看看迭代器的源码，果不其然，就发现了问题所在：
     *  在用Iterator循环删除的时候，调用的是ArrayList里面的remove方法，
     *  删除元素后modCount会增加，expectedModCount则不变，这样就造成了
     *  expectedModCount ！= modCount，那么就抛出异常了。
     *
     *  2、再用Iterator中的remove方法
     *  运行结果：
     *
     * [111, 333, 444, 333]
     * 发现，删除成功且没有报错。
     *
     *  什么原因呢？我们调用的了Iterator中的迭代器删除元素，在这个方法中有：
     *  expectedModCount = modCount这样一句代码，所以当我们每删除一次元素，
     *  就同步一次，所以调用checkForComodification()时，就不会报错。如果换到多线程中，
     *  这个方法不能保证两个变量修改的一致性，结果具有不确定性，所以不推荐这种方法。
     *
     *
     *  总结：Iterator调用ArrayList的删除方法报错，
     *      Iterator调用迭代器自己的删除方法，单线程不会报错，多线程会报错。
     *
     */
    public static void iteratorDelete() {
        String deleteEle = "222";
//        String deleteEle = "333";
        //foreach循环删除
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            if (iterator.next().equals("222")){
                list.remove(iterator.next());
//                // 再用Iterator中的remove方法
//                iterator.remove();
            }
        }
        System.out.println(Arrays.toString(list.toArray()));
    }

    /**
     * forEach循环删除
     *
     * 运行结果：
     *
     * Exception in thread "main" java.util.ConcurrentModificationException
     *     at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
     *     at java.util.ArrayList$Itr.next(ArrayList.java:859)
     *     at joe.effective.Test.main(Test6.java:20)
     * 报错。
     *
     * 原因：foreach原理是因为这些集合类都实现了Iterable接口，
     *      该接口中定义了Iterator迭代器的产生方法，
     *      并且foreach就是通过Iterable接口在序列中进行移动。
     *      也就是说：在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用
     *
     *      明白了原理就跟上述的Iterator删除调用ArrayList中remove一样了。
     *
     * 结论：forEach循环删除报错。
     *
     */
    public static void forEachDelete() {
        String deleteEle = "222";
//        String deleteEle = "333";
        // foreach循环删除
        for (String str : list){
            if (str.equals("222")){
                list.remove(str);
            }
        }

        System.out.println(Arrays.toString(list.toArray()));
    }


}
